/*
 * libid3tag - ID3 tag manipulation library
 * Copyright (C) 2000-2004 Underbit Technologies, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * $Id: field.c,v 1.16 2004/01/23 09:41:32 rob Exp $
 */

# ifdef HAVE_CONFIG_H
#  include "config.h"
# endif

# include "global.h"

# include <stdlib.h>
# include <string.h>

# ifdef HAVE_ASSERT_H
#  include <assert.h>
# endif

# include "id3tag.h"
# include "field.h"
# include "frame.h"
# include "render.h"
# include "ucs4.h"
# include "latin1.h"
# include "parse.h"

/*
 * NAME:    field->init()
 * DESCRIPTION: initialize a field to a default value for the given type
 */
void id3_field_init(union id3_field *field, enum id3_field_type type)
{
    assert(field);

    switch (field->type = type)
    {
    case ID3_FIELD_TYPE_TEXTENCODING:
    case ID3_FIELD_TYPE_INT8:
    case ID3_FIELD_TYPE_INT16:
    case ID3_FIELD_TYPE_INT24:
    case ID3_FIELD_TYPE_INT32:
        field->number.value = 0;
        break;

    case ID3_FIELD_TYPE_LATIN1:
    case ID3_FIELD_TYPE_LATIN1FULL:
        field->latin1.ptr = 0;
        break;

    case ID3_FIELD_TYPE_LATIN1LIST:
        field->latin1list.nstrings = 0;
        field->latin1list.strings  = 0;

    case ID3_FIELD_TYPE_STRING:
    case ID3_FIELD_TYPE_STRINGFULL:
        field->string.ptr = 0;
        break;

    case ID3_FIELD_TYPE_STRINGLIST:
        field->stringlist.nstrings = 0;
        field->stringlist.strings  = 0;
        break;

    case ID3_FIELD_TYPE_LANGUAGE:
        strcpy(field->immediate.value, "XXX");
        break;

    case ID3_FIELD_TYPE_FRAMEID:
        strcpy(field->immediate.value, "XXXX");
        break;

    case ID3_FIELD_TYPE_DATE:
        memset(field->immediate.value, 0, sizeof(field->immediate.value));
        break;

    case ID3_FIELD_TYPE_INT32PLUS:
    case ID3_FIELD_TYPE_BINARYDATA:
        field->binary.data   = 0;
        field->binary.length = 0;
        break;
    }
}

/*
 * NAME:    field->finish()
 * DESCRIPTION: reset a field, deallocating memory if necessary
 */
void id3_field_finish(union id3_field *field)
{
    unsigned int i;

    assert(field);

    switch (field->type)
    {
    case ID3_FIELD_TYPE_TEXTENCODING:
    case ID3_FIELD_TYPE_INT8:
    case ID3_FIELD_TYPE_INT16:
    case ID3_FIELD_TYPE_INT24:
    case ID3_FIELD_TYPE_INT32:
    case ID3_FIELD_TYPE_LANGUAGE:
    case ID3_FIELD_TYPE_FRAMEID:
    case ID3_FIELD_TYPE_DATE:
        break;

    case ID3_FIELD_TYPE_LATIN1:
    case ID3_FIELD_TYPE_LATIN1FULL:
        if (field->latin1.ptr)
            free(field->latin1.ptr);
        break;

    case ID3_FIELD_TYPE_LATIN1LIST:
        for (i = 0; i < field->latin1list.nstrings; ++i)
            free(field->latin1list.strings[i]);

        if (field->latin1list.strings)
            free(field->latin1list.strings);
        break;

    case ID3_FIELD_TYPE_STRING:
    case ID3_FIELD_TYPE_STRINGFULL:
        if (field->string.ptr)
            free(field->string.ptr);
        break;

    case ID3_FIELD_TYPE_STRINGLIST:
        for (i = 0; i < field->stringlist.nstrings; ++i)
            free(field->stringlist.strings[i]);

        if (field->stringlist.strings)
            free(field->stringlist.strings);
        break;

    case ID3_FIELD_TYPE_INT32PLUS:
    case ID3_FIELD_TYPE_BINARYDATA:
        if (field->binary.data)
            free(field->binary.data);
        break;
    }

    id3_field_init(field, field->type);
}

/*
 * NAME:    field->type()
 * DESCRIPTION: return the value type of a field
 */
enum id3_field_type id3_field_type(union id3_field const *field)
{
    assert(field);

    return field->type;
}

/*
 * NAME:    field->parse()
 * DESCRIPTION: parse a field value
 */
int id3_field_parse(union id3_field *field, id3_byte_t const **ptr,
                    id3_length_t length, enum id3_field_textencoding *encoding)
{
    assert(field);

    id3_field_finish(field);

    switch (field->type)
    {
    case ID3_FIELD_TYPE_INT32:
        if (length < 4)
            goto fail;

        field->number.value = id3_parse_uint(ptr, 4);
        break;

    case ID3_FIELD_TYPE_INT24:
        if (length < 3)
            goto fail;

        field->number.value = id3_parse_uint(ptr, 3);
        break;

    case ID3_FIELD_TYPE_INT16:
        if (length < 2)
            goto fail;

        field->number.value = id3_parse_uint(ptr, 2);
        break;

    case ID3_FIELD_TYPE_INT8:
    case ID3_FIELD_TYPE_TEXTENCODING:
        if (length < 1)
            goto fail;

        field->number.value = id3_parse_uint(ptr, 1);

        if (field->type == ID3_FIELD_TYPE_TEXTENCODING)
            *encoding = field->number.value;
        break;

    case ID3_FIELD_TYPE_LANGUAGE:
        if (length < 3)
            goto fail;

        id3_parse_immediate(ptr, 3, field->immediate.value);
        break;

    case ID3_FIELD_TYPE_FRAMEID:
        if (length < 4)
            goto fail;

        id3_parse_immediate(ptr, 4, field->immediate.value);
        break;

    case ID3_FIELD_TYPE_DATE:
        if (length < 8)
            goto fail;

        id3_parse_immediate(ptr, 8, field->immediate.value);
        break;

    case ID3_FIELD_TYPE_LATIN1:
    case ID3_FIELD_TYPE_LATIN1FULL:
    {
        id3_latin1_t *latin1;

        latin1 = id3_parse_latin1(ptr, length,
                                  field->type == ID3_FIELD_TYPE_LATIN1FULL);
        if (latin1 == 0)
            goto fail;

        field->latin1.ptr = latin1;
    }
    break;

    case ID3_FIELD_TYPE_LATIN1LIST:
    {
        id3_byte_t const *end;
        id3_latin1_t     *latin1, **strings;

        end = *ptr + length;

        while (end - *ptr > 0)
        {
            latin1 = id3_parse_latin1(ptr, end - *ptr, 0);
            if (latin1 == 0)
                goto fail;

            strings = realloc(field->latin1list.strings,
                              (field->latin1list.nstrings + 1) * sizeof(*strings));
            if (strings == 0)
            {
                free(latin1);
                goto fail;
            }

            field->latin1list.strings                               = strings;
            field->latin1list.strings[field->latin1list.nstrings++] = latin1;
        }
    }
    break;

    case ID3_FIELD_TYPE_STRING:
    case ID3_FIELD_TYPE_STRINGFULL:
    {
        id3_ucs4_t *ucs4;

        ucs4 = id3_parse_string(ptr, length, *encoding,
                                field->type == ID3_FIELD_TYPE_STRINGFULL);
        if (ucs4 == 0)
            goto fail;

        field->string.ptr = ucs4;
    }
    break;

    case ID3_FIELD_TYPE_STRINGLIST:
    {
        id3_byte_t const *end;
        id3_ucs4_t       *ucs4, **strings;

        end = *ptr + length;

        while (end - *ptr > 0)
        {
            ucs4 = id3_parse_string(ptr, end - *ptr, *encoding, 0);
            if (ucs4 == 0)
                goto fail;

            strings = realloc(field->stringlist.strings,
                              (field->stringlist.nstrings + 1) * sizeof(*strings));
            if (strings == 0)
            {
                free(ucs4);
                goto fail;
            }

            field->stringlist.strings                               = strings;
            field->stringlist.strings[field->stringlist.nstrings++] = ucs4;
        }
    }
    break;

    case ID3_FIELD_TYPE_INT32PLUS:
    case ID3_FIELD_TYPE_BINARYDATA:
    {
        id3_byte_t *data;

        data = id3_parse_binary(ptr, length);
        if (data == 0)
            goto fail;

        field->binary.data   = data;
        field->binary.length = length;
    }
    break;
    }

    return 0;

 fail:
    return -1;
}

/*
 * NAME:    field->render()
 * DESCRIPTION: render a field value
 */
id3_length_t id3_field_render(union id3_field const *field, id3_byte_t **ptr,
                              enum id3_field_textencoding *encoding,
                              int terminate)
{
    id3_length_t size;
    unsigned int i;

    assert(field && encoding);

    switch (field->type)
    {
    case ID3_FIELD_TYPE_INT32:
        return id3_render_int(ptr, field->number.value, 4);

    case ID3_FIELD_TYPE_INT24:
        return id3_render_int(ptr, field->number.value, 3);

    case ID3_FIELD_TYPE_INT16:
        return id3_render_int(ptr, field->number.value, 2);

    case ID3_FIELD_TYPE_TEXTENCODING:
        *encoding = field->number.value;
    case ID3_FIELD_TYPE_INT8:
        return id3_render_int(ptr, field->number.value, 1);

    case ID3_FIELD_TYPE_LATIN1:
    case ID3_FIELD_TYPE_LATIN1FULL:
        return id3_render_latin1(ptr, field->latin1.ptr, terminate);

    case ID3_FIELD_TYPE_LATIN1LIST:
        size = 0;
        for (i = 0; i < field->latin1list.nstrings; ++i)
        {
            size += id3_render_latin1(ptr, field->latin1list.strings[i],
                                      (i < field->latin1list.nstrings - 1) ||
                                      terminate);
        }
        return size;

    case ID3_FIELD_TYPE_STRING:
    case ID3_FIELD_TYPE_STRINGFULL:
        return id3_render_string(ptr, field->string.ptr, *encoding, terminate);

    case ID3_FIELD_TYPE_STRINGLIST:
        size = 0;
        for (i = 0; i < field->stringlist.nstrings; ++i)
        {
            size += id3_render_string(ptr, field->stringlist.strings[i], *encoding,
                                      (i < field->stringlist.nstrings - 1) ||
                                      terminate);
        }
        return size;

    case ID3_FIELD_TYPE_LANGUAGE:
        return id3_render_immediate(ptr, field->immediate.value, 3);

    case ID3_FIELD_TYPE_FRAMEID:
        return id3_render_immediate(ptr, field->immediate.value, 4);

    case ID3_FIELD_TYPE_DATE:
        return id3_render_immediate(ptr, field->immediate.value, 8);

    case ID3_FIELD_TYPE_INT32PLUS:
    case ID3_FIELD_TYPE_BINARYDATA:
        return id3_render_binary(ptr, field->binary.data, field->binary.length);
    }

    return 0;
}

/*
 * NAME:    field->setint()
 * DESCRIPTION: set the value of an int field
 */
int id3_field_setint(union id3_field *field, signed long number)
{
    assert(field);

    switch (field->type)
    {
    case ID3_FIELD_TYPE_INT8:
        if (number > 0x7f || number < -0x80)
            return -1;
        break;

    case ID3_FIELD_TYPE_INT16:
        if (number > 0x7fff || number < -0x8000)
            return -1;
        break;

    case ID3_FIELD_TYPE_INT24:
        if (number > 0x7fffffL || number < -0x800000L)
            return -1;
        break;

    case ID3_FIELD_TYPE_INT32:
        if (number > 0x7fffffffL || number < -0x80000000L)
            return -1;
        break;

    default:
        return -1;
    }

    id3_field_finish(field);

    field->number.value = number;

    return 0;
}

/*
 * NAME:    field->settextencoding()
 * DESCRIPTION: set the value of a textencoding field
 */
int id3_field_settextencoding(union id3_field *field,
                              enum id3_field_textencoding encoding)
{
    assert(field);

    if (field->type != ID3_FIELD_TYPE_TEXTENCODING)
        return -1;

    id3_field_finish(field);

    field->number.value = encoding;

    return 0;
}

static
int set_latin1(union id3_field *field, id3_latin1_t const *latin1)
{
    id3_latin1_t *data;

    if (latin1 == 0 || *latin1 == 0)
        data = 0;
    else
    {
        data = id3_latin1_duplicate(latin1);
        if (data == 0)
            return -1;
    }

    field->latin1.ptr = data;

    return 0;
}

/*
 * NAME:    field->setlatin1()
 * DESCRIPTION: set the value of a latin1 field
 */
int id3_field_setlatin1(union id3_field *field, id3_latin1_t const *latin1)
{
    assert(field);

    if (field->type != ID3_FIELD_TYPE_LATIN1)
        return -1;

    id3_field_finish(field);

    if (latin1)
    {
        id3_latin1_t const *ptr;

        for (ptr = latin1; *ptr; ++ptr)
        {
            if (*ptr == '\n')
                return -1;
        }
    }

    return set_latin1(field, latin1);
}

/*
 * NAME:    field->setfulllatin1()
 * DESCRIPTION: set the value of a full latin1 field
 */
int id3_field_setfulllatin1(union id3_field *field, id3_latin1_t const *latin1)
{
    assert(field);

    if (field->type != ID3_FIELD_TYPE_LATIN1FULL)
        return -1;

    id3_field_finish(field);

    return set_latin1(field, latin1);
}

static
int set_string(union id3_field *field, id3_ucs4_t const *string)
{
    id3_ucs4_t *data;

    if (string == 0 || *string == 0)
        data = 0;
    else
    {
        data = id3_ucs4_duplicate(string);
        if (data == 0)
            return -1;
    }

    field->string.ptr = data;

    return 0;
}

/*
 * NAME:    field->setstring()
 * DESCRIPTION: set the value of a string field
 */
int id3_field_setstring(union id3_field *field, id3_ucs4_t const *string)
{
    assert(field);

    if (field->type != ID3_FIELD_TYPE_STRING)
        return -1;

    id3_field_finish(field);

    if (string)
    {
        id3_ucs4_t const *ptr;

        for (ptr = string; *ptr; ++ptr)
        {
            if (*ptr == '\n')
                return -1;
        }
    }

    return set_string(field, string);
}

/*
 * NAME:    field->setfullstring()
 * DESCRIPTION: set the value of a full string field
 */
int id3_field_setfullstring(union id3_field *field, id3_ucs4_t const *string)
{
    assert(field);

    if (field->type != ID3_FIELD_TYPE_STRINGFULL)
        return -1;

    id3_field_finish(field);

    return set_string(field, string);
}

/*
 * NAME:    field->setstrings()
 * DESCRIPTION: set the value of a stringlist field
 */
int id3_field_setstrings(union id3_field *field,
                         unsigned int length, id3_ucs4_t **ptrs)
{
    id3_ucs4_t   **strings;
    unsigned int i;

    assert(field);

    if (field->type != ID3_FIELD_TYPE_STRINGLIST)
        return -1;

    id3_field_finish(field);

    if (length == 0)
        return 0;

    strings = malloc(length * sizeof(*strings));
    if (strings == 0)
        return -1;

    for (i = 0; i < length; ++i)
    {
        strings[i] = id3_ucs4_duplicate(ptrs[i]);
        if (strings[i] == 0)
        {
            while (i--)
                free(strings[i]);

            free(strings);
            return -1;
        }
    }

    field->stringlist.strings  = strings;
    field->stringlist.nstrings = length;

    return 0;
}

/*
 * NAME:    field->addstring()
 * DESCRIPTION: add a string to a stringlist field
 */
int id3_field_addstring(union id3_field *field, id3_ucs4_t const *string)
{
    id3_ucs4_t *new, **strings;

    assert(field);

    if (field->type != ID3_FIELD_TYPE_STRINGLIST)
        return -1;

    if (string == 0)
        string = id3_ucs4_empty;

    new = id3_ucs4_duplicate(string);
    if (new == 0)
        return -1;

    strings = realloc(field->stringlist.strings,
                      (field->stringlist.nstrings + 1) * sizeof(*strings));
    if (strings == 0)
    {
        free(new);
        return -1;
    }

    field->stringlist.strings                               = strings;
    field->stringlist.strings[field->stringlist.nstrings++] = new;

    return 0;
}

/*
 * NAME:    field->setlanguage()
 * DESCRIPTION: set the value of a language field
 */
int id3_field_setlanguage(union id3_field *field, char const *language)
{
    assert(field);

    if (field->type != ID3_FIELD_TYPE_LANGUAGE)
        return -1;

    id3_field_finish(field);

    if (language)
    {
        if (strlen(language) != 3)
            return -1;

        strcpy(field->immediate.value, language);
    }

    return 0;
}

/*
 * NAME:    field->setframeid()
 * DESCRIPTION: set the value of a frameid field
 */
int id3_field_setframeid(union id3_field *field, char const *id)
{
    assert(field);

    if (field->type != ID3_FIELD_TYPE_FRAMEID ||
        !id3_frame_validid(id))
        return -1;

    id3_field_finish(field);

    field->immediate.value[0] = id[0];
    field->immediate.value[1] = id[1];
    field->immediate.value[2] = id[2];
    field->immediate.value[3] = id[3];
    field->immediate.value[4] = 0;

    return 0;
}

/*
 * NAME:    field->setbinarydata()
 * DESCRIPTION: set the value of a binarydata field
 */
int id3_field_setbinarydata(union id3_field *field,
                            id3_byte_t const *data, id3_length_t length)
{
    id3_byte_t *mem;

    assert(field);

    if (field->type != ID3_FIELD_TYPE_BINARYDATA)
        return -1;

    id3_field_finish(field);

    if (length == 0)
        mem = 0;
    else
    {
        mem = malloc(length);
        if (mem == 0)
            return -1;

        assert(data);

        memcpy(mem, data, length);
    }

    field->binary.data   = mem;
    field->binary.length = length;

    return 0;
}

/*
 * NAME:    field->getint()
 * DESCRIPTION: return the value of an integer field
 */
signed long id3_field_getint(union id3_field const *field)
{
    assert(field);

    if (field->type != ID3_FIELD_TYPE_INT8 &&
        field->type != ID3_FIELD_TYPE_INT16 &&
        field->type != ID3_FIELD_TYPE_INT24 &&
        field->type != ID3_FIELD_TYPE_INT32)
        return -1;

    return field->number.value;
}

/*
 * NAME:    field->gettextencoding()
 * DESCRIPTION: return the value of a text encoding field
 */
enum id3_field_textencoding
id3_field_gettextencoding(union id3_field const *field)
{
    assert(field);

    if (field->type != ID3_FIELD_TYPE_TEXTENCODING)
        return -1;

    return field->number.value;
}

/*
 * NAME:    field->getlatin1()
 * DESCRIPTION: return the value of a latin1 field
 */
id3_latin1_t const *id3_field_getlatin1(union id3_field const *field)
{
    assert(field);

    if (field->type != ID3_FIELD_TYPE_LATIN1)
        return 0;

    return field->latin1.ptr ? field->latin1.ptr : (id3_latin1_t const *) "";
}

/*
 * NAME:    field->getfulllatin1()
 * DESCRIPTION: return the value of a full latin1 field
 */
id3_latin1_t const *id3_field_getfulllatin1(union id3_field const *field)
{
    assert(field);

    if (field->type != ID3_FIELD_TYPE_LATIN1FULL)
        return 0;

    return field->latin1.ptr ? field->latin1.ptr : (id3_latin1_t const *) "";
}

/*
 * NAME:    field->getstring()
 * DESCRIPTION: return the value of a string field
 */
id3_ucs4_t const *id3_field_getstring(union id3_field const *field)
{
    assert(field);

    if (field->type != ID3_FIELD_TYPE_STRING)
        return 0;

    return field->string.ptr ? field->string.ptr : id3_ucs4_empty;
}

/*
 * NAME:    field->getfullstring()
 * DESCRIPTION: return the value of a fullstring field
 */
id3_ucs4_t const *id3_field_getfullstring(union id3_field const *field)
{
    assert(field);

    if (field->type != ID3_FIELD_TYPE_STRINGFULL)
        return 0;

    return field->string.ptr ? field->string.ptr : id3_ucs4_empty;
}

/*
 * NAME:    field->getnstrings()
 * DESCRIPTION: return the number of strings in a stringlist field
 */
unsigned int id3_field_getnstrings(union id3_field const *field)
{
    assert(field);

    if (field->type != ID3_FIELD_TYPE_STRINGLIST)
        return 0;

    return field->stringlist.nstrings;
}

/*
 * NAME:    field->getstrings()
 * DESCRIPTION: return one value of a stringlist field
 */
id3_ucs4_t const *id3_field_getstrings(union id3_field const *field,
                                       unsigned int index)
{
    id3_ucs4_t const *string;

    assert(field);

    if (field->type != ID3_FIELD_TYPE_STRINGLIST ||
        index >= field->stringlist.nstrings)
        return 0;

    string = field->stringlist.strings[index];

    return string ? string : id3_ucs4_empty;
}

/*
 * NAME:    field->getframeid()
 * DESCRIPTION: return the value of a frameid field
 */
char const *id3_field_getframeid(union id3_field const *field)
{
    assert(field);

    if (field->type != ID3_FIELD_TYPE_FRAMEID)
        return 0;

    return field->immediate.value;
}

/*
 * NAME:    field->getbinarydata()
 * DESCRIPTION: return the value of a binarydata field
 */
id3_byte_t const *id3_field_getbinarydata(union id3_field const *field,
                                          id3_length_t *length)
{
    static id3_byte_t const empty;

    assert(field && length);

    if (field->type != ID3_FIELD_TYPE_BINARYDATA)
        return 0;

    assert(field->binary.length == 0 || field->binary.data);

    *length = field->binary.length;

    return field->binary.data ? field->binary.data : &empty;
}
